#include <linux/syscalls.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <net/ip.h>
#include <asm/unistd.h>
#include "../include/pub.h"
#include "uf_rule.h"
#include "uf_syscall.h"

unsigned long **entry;
unsigned long *urlfilter_old;

typedef int (*PF_UF_syscall)
(
    unsigned int optcode,
    void *input, unsigned int inputlen, 
    void *output, unsigned int outputlen
);

PF_UF_syscall UF_kernel_cmdlist[UF_CMD_MAX] = 
{
    [UF_CMD_ADDURL] = UF_entry_addurl,
    [UF_CMD_DELURL] = UF_entry_delurl,
    [UF_CMD_SHOW]   = UF_entry_show,
    [UF_CMD_ACTION] = UF_entry_action,
    [UF_CMD_HTTPS] = UF_entry_https,   
    [UF_CMD_DEBUG] = UF_entry_debug,
};

/* UF_syscall */
asmlinkage long UF_syscall(unsigned int optcode,
                           void *input, unsigned int inputlen, 
                           void *output, unsigned int outputlen)
{
    long ret = -1;
    void *kernel_input = NULL;
    void *kernel_output = NULL;

    if (optcode >= UF_CMD_MAX)
    {
        printk(KERN_EMERG"invalid optcode!\n");
        return -1;
    }

    if ((NULL != input) && (0 != inputlen))
    {
        if (NULL != (kernel_input = kzalloc(inputlen, GFP_ATOMIC)))
        {
            copy_from_user(kernel_input, input, inputlen);
        }
    }

    if ((NULL != output) && (0 != outputlen))
    {
        if (NULL != (kernel_output = kzalloc(outputlen, GFP_ATOMIC)))
        {
            copy_from_user(kernel_output, output, outputlen);
        }
    }
    
    if (NULL != UF_kernel_cmdlist[optcode])
    {
        ret = UF_kernel_cmdlist[optcode](optcode, kernel_input, inputlen,
                                         kernel_output, outputlen);
    }

    if (kernel_input)
    {
        copy_to_user(input, kernel_input, inputlen);
    }
    if (kernel_output)
    {
        copy_to_user(output, kernel_output, outputlen);
    }

    return ret;
}

/* get sys_call_table */
unsigned long **get_sys_call_table(void)
{
    entry = (unsigned long **)PAGE_OFFSET;
    
    for (; (unsigned long)entry < ULONG_MAX; entry += 1) 
    {
        /* #define __NR_close  6 */
        if (entry[__NR_close] == (unsigned long *)sys_close) 
        {
            return entry;
        }
    }
    
    return NULL;
}

void disable_write_protection(void)
{
    unsigned long cr0 = read_cr0();
    clear_bit(16, &cr0);
    write_cr0(cr0);
    return ;
}

void enable_write_protection(void)
{
    unsigned long cr0 = read_cr0();
    set_bit(16, &cr0);
    write_cr0(cr0);
    return ;
}

/* setup __NR_urlfilter */
int UF_setsyscall(void)
{   
    entry = get_sys_call_table();
    if (NULL == entry)
    {
        printk(KERN_EMERG"get_sys_call_table fail\r\n");
        return -1;
    }
    
    urlfilter_old = (unsigned long *)(entry[__NR_urlfilter]);
    
    disable_write_protection();
    entry[__NR_urlfilter] = (unsigned long *)UF_syscall;
    enable_write_protection();
    
    return 0;
}

/* reset __NR_urlfilter */
void UF_resetsyscall(void)
{    
    disable_write_protection();
    entry[__NR_urlfilter] = urlfilter_old;
    enable_write_protection();
    return ;
}

